﻿/*
 * Jonathan
 * Created by: Jonathan Montiverdi
 * Date: 7/16/2012
 * License: http://jonathan.codeplex.com/license
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using Awesomium.Core;
using Jonathan.Data;
using Jonathan.DataAccess.Sqlite;
using Monti.Docking;
using Monti.MVVM.Services;

namespace Jonathan.Logic
{
   /// <summary>
      /// Initializes a new search logic object.
   /// </summary>
   public class SearchViewModel : DockItem, IDataViewerModel
   {
      #region Fields
      
      private IMain m_Main;
      private IOptions m_Options;
      private string m_Contents;
      private static readonly Regex s_HtmlRemover = new Regex ("<([\\w\\s\"=#/\\|-]+)>", RegexOptions.IgnoreCase);
      
      private string m_Html;
      
      /// <summary>
      /// Event used to notify the item is closing.
      /// </summary>
      public event EventHandler ItemClosing;
      
      /// <summary>
      /// Event used to execute an html change request.
      /// </summary>
      public event HtmlChangeEventHandler HtmlChangeRequested;
      
      #endregion
      
      #region Setup
      
      /// <summary>
      /// Initializes a new search object.
      /// </summary>
      public SearchViewModel ()
      {
         this.Title = "Search";
         this.Excerpts = new ObservableCollection<Excerpt> ();
      }
      
      #endregion
      
      #region Properties
      
      /// <summary>
      /// Gets the text we are searching for.
      /// </summary>
      public string SearchText { get; private set; }

      /// <summary>
      /// Gets the list of current excerpts.
      /// </summary>
      public ObservableCollection<Excerpt> Excerpts { get; private set; }
      
      /// <summary>
      /// Gets the main application.
      /// </summary>
      [SqliteIgnore]
      public IMain Main { get { return this.m_Main ?? (this.m_Main = ServiceManager.Resolve <IMain> ()); } }
      
      /// <summary>
      /// Gets the options of the application.
      /// </summary>
      [SqliteIgnore]
      public IOptions Options { get { return this.m_Options ?? (this.m_Options = ServiceManager.Resolve <IOptions> ()); } }
      
      /// <summary>
      /// Gets or Sets the html to display in the data viewer.
      /// </summary>
      [SqliteIgnore]
      public string Html
      {
         get { return this.m_Html; }
         set
         {
            if (this.m_Html != value)
            {
               this.m_Html = value;
               if (this.HtmlChangeRequested != null)
                  this.HtmlChangeRequested (new HtmlChangeEventArgs { TagId = "Html", Content = value });
               this.RaisePropertyChanged ("Html");
            }
         }
      }
      
      #endregion
      
      #region Methods
      
      /// <summary>
      /// Gets the list of a global javascript callbacks if any.
      /// </summary>
      public IDictionary <string, JSCallback> GetCallbacks ()
      {
         return null;
      }
      
      /// <summary>
      /// Performs a search with the given text.
      /// </summary>
      /// <param name="text"></param>
      public void Search (string text)
      {
         this.SearchText = text;
         this.Title = "Search - " + text;
         
         Action <string> searchDel = this.RunSearch;
         searchDel.BeginInvoke (text, this.SearchDone, null);
      }

      /// <summary>
      /// Called when a search is completed.
      /// </summary>
      /// <param name="ar">IAsyncResult</param>
      private void SearchDone (IAsyncResult ar)
      {
         this.BuildHtml ();
         //MainViewModel.GuiDispatcher.BeginInvoke (new Action (() =>this.BuildHtml ()), null);
      }
      
      /// <summary>
      /// Runs the search in a thread safe method.
      /// </summary>
      /// <param name="text">Text to search for.</param>
      private void RunSearch (string text)
      {
         int bookCount = 0;
         var contents = new StringBuilder ();
         contents.AppendLine ("<ul>");
         // TODO: Report progress...
         var replacer = new Regex (text, RegexOptions.IgnoreCase);
         foreach (var book in this.Options.Books.Where (b => b.FileExists && b.Name != Common.DEF_DB_NAME))
         {
            var excerpts = Sqlite.Read <Excerpt> (book.FullPath, "WHERE Passage LIKE '%" + text + "%' ORDER BY Chapter, Verse").ToList ();
            var excerptCount = excerpts.Count;
            if (excerptCount > 0)
            {
               contents.AppendLine (string.Format ("<li><input type='checkbox' id='item-{0}-0' />" +
                                                   "<label for='item-{0}-0'>{1} ({2})</label>",
                                                   bookCount, book.Name, excerptCount));
               contents.AppendLine ("<ul>");
               bookCount++;
            }
            
            foreach (var excerpt in excerpts)
            {
               excerpt.Book = book.Name;
               // TODO: search for a word in a definition template does not work with the html remove (cowed)...
               var noHtml = s_HtmlRemover.Replace (excerpt.Passage, this.HtmlRemoveMatch);
               var matches = replacer.Matches (noHtml);
               for (int a = matches.Count - 1; a >= 0; a--)
               {
                  var match = matches [a];
                  excerpt.Passage = excerpt.Passage.Insert (match.Index + match.Length, "</span>");
                  excerpt.Passage = excerpt.Passage.Insert (match.Index, "<span class=\"label label-warning\">");
               }
               contents.AppendLine (string.Format ("<li><a href=\"#{3}{0}{1}{2}\">{0}{1}{4}</a></li>",
                                                   excerpt.HasSubBook ? excerpt.SubBook + ":" : string.Empty,
                                                   excerpt.HasChapter ? excerpt.Chapter + ":" : string.Empty,
                                                   excerpt.Verse,
                                                   book.Name,
                                                  (excerpt.HasSubBook || excerpt.HasChapter)
                                                  ? excerpt.Verse.ToString (CultureInfo.InvariantCulture)
                                                  : "Verse " + excerpt.Verse.ToString (CultureInfo.InvariantCulture)));
               MainViewModel.GuiDispatcher.Invoke (new Action <Excerpt> (e =>this.Excerpts.Add (e)), excerpt);
            }
            if (excerptCount > 0) contents.AppendLine ("</ul></li>");
         }
         contents.AppendLine ("</ul>");
         this.m_Contents = contents.ToString ();
      }
      
      /// <summary>
      /// Match evaluator for removing html.
      /// </summary>
      /// <param name="match">Match needing evaluation.</param>
      /// <returns>Formatted replacement string.</returns>
      private string HtmlRemoveMatch (Match match)
      {
         return new string (' ', match.Length);
      }
      
      /// <summary>
      /// Builds the current Html string for the viewer.
      /// </summary>
      private void BuildHtml ()
      {
         var lastBook = string.Empty;
         var html = new StringBuilder ();
         html.AppendLine ("<html>");
         
         html.AppendLine (Common.GetHtmlHead ("Search - " + this.SearchText));
         
         html.AppendLine ("<div id='header'>");
         html.AppendLine ("</div>");
         
         html.AppendLine ("<div id='contentsTitle' class=\"label label-info\">List of Matches</div>");
         
         html.AppendLine ("<div id='contentsTree'>");
         html.AppendLine ("<div class='treeview'>");
         html.AppendLine (this.m_Contents);
         html.AppendLine ("</div>");
         html.AppendLine ("</div>");

         html.AppendLine ("<div id='mainContentTitle' class=\"label label-info\">");
         html.AppendLine (string.Format ("{0} matches found.", this.Excerpts.Count));
         html.AppendLine ("</div>");
         
         html.AppendLine ("<div id='mainContent'>");
         foreach (var excerpt in this.Excerpts)
         {
            if (excerpt.Book != lastBook)
            {
               if (!string.IsNullOrEmpty (lastBook))
                  html.AppendLine ("</table>");
               html.AppendLine ("<div class=\"alert alert-info\" style=\"width=100%; margin-bottom: 0px;\"><h4 class=\"alert-heading\">");
               html.AppendLine (excerpt.Book + "</h4></div>");
               html.AppendLine ("<table width=\"100%\" class=\"table table-striped\">");
               lastBook = excerpt.Book;
            }
            html.AppendLine ("<tr>");
            html.AppendLine ("<td>");
            html.Append (string.Format ("<span id=\"{0}{1}{2}{3}\"></span>", excerpt.Book,
                                        excerpt.HasSubBook ? excerpt.SubBook + ":" : string.Empty,
                                        excerpt.HasChapter ? excerpt.Chapter + ":" : string.Empty,
                                        excerpt.Verse));
            html.Append ("<a class=\"btn btn-inverse\" style=\"width: 95px;\" ");
            html.Append (string.Format ("href=\"{0}:{1}:{2}:{3}\">", excerpt.Book,
                                        excerpt.HasSubBook ? excerpt.SubBook : string.Empty,
                                        excerpt.HasChapter ? excerpt.Chapter : string.Empty,
                                        excerpt.Verse));
            html.AppendLine (string.Format ("<dl class=\"horDl\"><dt>{0} {1}{2}{3}</dt><dd><i class=\"icon-share-alt icon-white\"></i></dd></dl>",
                                            excerpt.Book,
                                            excerpt.HasSubBook ? excerpt.SubBook + " " : string.Empty,
                                            excerpt.HasChapter ? excerpt.Chapter + ":" : string.Empty,
                                            excerpt.Verse));
            html.AppendLine ("</a></td>");
            html.AppendLine ("<td>");
            html.AppendLine (excerpt.Passage);
            html.AppendLine ("</td>");
            html.AppendLine ("</tr>");
         }
         html.AppendLine ("</table>");
         html.AppendLine ("</div>");
         
         html.AppendLine ("</body>");
         html.AppendLine ("</html>");
         
         this.Html = html.ToString ();
      }
      
      /// <summary>
      /// Opens the given link.
      /// </summary>
      /// <param name="linkToOpen">Link to data to open.</param>
      public void Open (string linkToOpen)
      {
         var group = this.DockGroup.Children.FirstOrDefault (g => g.DockArea == DockArea.Right);
         if (group == null)
         {
            group = new DockGroup { DockArea = DockArea.Right };
            this.DockGroup.Children.Add (group);
         }
         var link = new Link (linkToOpen);
         if (link.HasBook)
         {
            var book = this.Options.Books.First (b => b.Name.Equals (link.Book, StringComparison.CurrentCultureIgnoreCase));
            group.Items.Add (new ReaderViewModel (book, linkToOpen));
         }
      }
      
      /// <summary>
      /// Occurs when the tab is closing.
      /// </summary>
      /// <returns>True if ok to close, otherwise false.</returns>
      public override bool Closing ()
      {
         if (this.ItemClosing != null)
            this.ItemClosing (this, EventArgs.Empty);
         return true;
      }
      
      #endregion
   }
}
